/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package dk.itu.samd.group2.tagapastry;

import java.io.IOException;
import java.util.Iterator;
import java.util.Vector;

import rice.p2p.commonapi.Endpoint;
import rice.p2p.commonapi.rawserialization.InputBuffer;
import rice.p2p.commonapi.rawserialization.OutputBuffer;

import dk.itu.samd.group2.tagapastry.messages.IndexMsg;
import dk.itu.samd.group2.tagapastry.messages.TagapastryMsg;
import dk.itu.samd.group2.tagapastry.indexing.DistributionStrategy;
import dk.itu.samd.group2.tagapastry.indexing.RetractionStrategy;
import dk.itu.samd.group2.tagapastry.indexing.SimpleDistributionStrategy;
import dk.itu.samd.group2.tagapastry.indexing.SimpleRetractionStrategy;

/**
 *
 * @author frgr
 */
public class IndexImpl implements Index {

	private Tag tag;
	private Vector<IndexEntry> entries;

	private State state;

	// Buffers messages while index is retracting
	private Vector<TagapastryMsg> msgBuffer;

	private DistributionStrategy distStrat;
	private RetractionStrategy retStrat;

	// No need for them to be final - will be default
	// or specified by the user
	private static int expansionThreshold;
	private static int retractionThreshold;

	// Default values for threshold if not provided
	// by the user
	private static final int DEF_EXP_THRESHOLD = 100;
	private static final int DEF_RET_THRESHOLD = 80;

	private TagapastryImpl tagaImpl;

	private Vector<IndexRelative> children;
//	private IndexRelative parent;

	private int sizeOfSubtree;
	private int sizeDelta;
	private IndexRelative parent;
	private int sizeOfObject;
	private boolean useTest;

	// constructor
	// TODO The TagapastryImpl should ideally be of type Tagapastry.
	// IT is such to have access to the environment
	public IndexImpl(Tag tag, TagapastryImpl tagaImpl) {
		this(tag, tagaImpl, null);		
		this.sizeOfSubtree = 0;
	}

	// This constructor is called from IndexExpansionMsg
	public IndexImpl(Tag tag, TagapastryImpl tagaImpl, Vector<IndexEntry> entries) {
		init(tag,tagaImpl,entries);
		expansionCheck();
	}

	/*
	 * Index initialization based on the data supplied to constructor.
	 */
	private void init(Tag tag, TagapastryImpl impl, Vector<IndexEntry> ientries) {
		this.tag = tag;
		this.tagaImpl = impl;
		this.entries = ientries == null ? 
				new Vector<IndexEntry>() : ientries;
		this.distStrat = new SimpleDistributionStrategy(this.tagaImpl);
		this.retStrat = new SimpleRetractionStrategy(this.tagaImpl);
		this.state = State.LEAF;
		this.resetDelta();
		setThresholds(tagaImpl.getRetractionThreshold(), tagaImpl.getExpansionThreshold());
		tagaImpl.putIndex(this);
		this.useTest = false;
		this.msgBuffer = new Vector<TagapastryMsg>();
	}

	/*
	 * Deserialize index when receiving it from nodes in a leaf set
	 */
	// TODO should this not set the rest of the variables too?!
	public IndexImpl(InputBuffer buf, Endpoint endpoint) throws IOException {
		this.tagaImpl = null;	
		this.tag = new Tag(buf, endpoint);
		int sizeOfEntries = buf.readInt();
		this.entries = new Vector<IndexEntry>(sizeOfEntries);
		for(int i=0; i < sizeOfEntries; i++) {
			this.entries.add(new IndexEntryImpl(buf));
		}
		this.useTest = false;
	}

	/*
	 * Serialize index when sending it to nodes in a leafset
	 */
	public void serialize(OutputBuffer buf) throws IOException  {
		this.tag.serialize(buf);
		buf.writeInt(this.entries.size());
		for(IndexEntry ie : entries) {
			ie.serialize(buf);
		}
	}

	@Override
	public void insert(String object, String hash) {
		addEntry(object, hash);
		if (!this.isRoot()) {
			this.incDelta();
		}
		if (state == State.EMPTY_LEAF) {
			setState(State.LEAF);
		}
		expansionCheck();
	}

	/*
	 * Add or upgrade existing entry. Make sure you reorder content
	 */
	private void addEntry(String object, String hash) {
		for (int i = 0; i < entries.size(); i++) {
			IndexEntry entry = entries.get(i);
			if  (entry.getContent().equals(object) &&
					entry.getHash().equals(hash)) {
				entry.incrementRank();

				int indexToSwitch = i;
				while (indexToSwitch > 0 && 
						entry.getRank() > entries.get(indexToSwitch - 1).getRank()) {
					indexToSwitch--;
				}

				if (indexToSwitch != i) {
					IndexEntry temp = entries.get(indexToSwitch);
					entries.setElementAt(entry, indexToSwitch);
					entries.set(i, temp);
				}

				return;
			}
		}

		entries.add(new IndexEntryImpl(object,hash));
	}
	
	/*
	 * Adding for testing
	 */
	Vector<TagapastryMsg> getMessageBuffer() {
		return msgBuffer;
	}
	
	/*
	 * 
	 */
	State getState() {
		return state;
	}
	
	/*
	 * 
	 */
	void activateTesting() {
		this.useTest = true;
	}

	private void expansionCheck() {
		if (entries.size() >= expansionThreshold) {
			if (msgBuffer == null) {
				msgBuffer = new Vector<TagapastryMsg>();
			}
			if (children == null) {
				children = initChildren();
			}
			setState(State.PARENT);
			sizeOfSubtree = entries.size();
			// If not testing init distribution
			if(!useTest)
				distStrat.distribute(tag, entries, children);
		}
	}

	private Vector<IndexRelative> initChildren() {
		ChildrenMapper mapper = new ChildrenMapper();
		Vector<IndexRelative> children = new Vector<IndexRelative>(mapper.getChildrenNum());
		for (int i = 0; i < mapper.getChildrenNum(); i++) {
			Tag childTag = TagFactory.duplicateTagWithExtension(tag, mapper.getChar(i));
			IndexRelative child = new IndexRelativeImpl(childTag, IndexRelative.State.INACTIVE);
			children.add(child);
		}

		return children;
	}

	@Override
	public Vector<IndexEntry> getEntries() {
		return entries;	
	}

	@Override
	public Vector<IndexRelative> getChildren() {
		return children;
	}

	@Override
	public int getSize() {
		int size = state == State.PARENT || 
		state == State.RETRACTING ? sizeOfSubtree : entries.size();
		return size;
	}


	public Tag getTag() {
		return tag;
	}

	@Override
	public String toString() {
		return "index of " + tag.getKeyword();
	}

	public void printEntries() {
//		for(IndexEntry ie : entries) {
//			System.out.println(ie);
//		}
	}

	public void incDelta() {
		sizeDelta++;
	}

	@Override
	public void decDelta() {
		sizeDelta--;
	}

	@Override
	public void resetDelta() {
		sizeDelta = 0;
	}

	@Override
	public boolean isDeltaChanged() {
		return sizeDelta != 0;
	}

	public int getDelta() {
		return sizeDelta;
	}

	public void addToSizeOfSubtree(int delta) {
		this.sizeOfSubtree += delta;
		if (!this.isRoot()) {
			// Root does not care about delta. From the root
			// point of view, important is only size of the
			// whole subtree
			this.sizeDelta += delta;
		}
		//System.out.println("");
		if (this.sizeOfSubtree <= retractionThreshold) {
			setState(State.RETRACTING);
			//System.out.println("activation retraction strat");
			retStrat.retract(children);
		}
	}

	@Override
	public boolean isRoot() {
		return tag.getExtension().isEmpty();
	}

	void setThresholds(int ret, int exp) {
		if (ret >= 1 && exp > 1 && exp > ret) {
			retractionThreshold = ret;
			expansionThreshold = exp;
		} else {
			retractionThreshold = DEF_RET_THRESHOLD;
			expansionThreshold = DEF_EXP_THRESHOLD;
		}
	}

	@Override
	public void setEntries(Vector<IndexEntry> entries) {
		this.entries = entries;
	}

	private void setState(State newState) {
		switch(newState) {
		case LEAF: 
			if (state == State.RETRACTING) {
				state = newState;
				tagaImpl.checkToStopMaintanceTask();
			}
			break;
		case EMPTY_LEAF:
			if (state == State.LEAF) {
				state = newState;
			}
		case PARENT:
			if (state == State.LEAF) {
				state = newState;
			}
			break;
		case RETRACTING:
			if (state == State.PARENT) {
				state = newState;
			}
			break;
		default:
			try {
				throw new Exception("Illegal change of state occured.");
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				System.exit(1);
			}
		}
	}

	@Override
	public void remove(String object, String hash) {

		for (int i = 0; i < entries.size(); i++) {
			IndexEntry entry = entries.get(i);

			if  (entry.getContent().equals(object) &&
					entry.getHash().equals(hash)) {
				entry.decrementRank();
				//				System.out.println("Removing entry " + entry);
				// remove if the rank is down to zero
				if (entry.getRank() == 0) {
					entries.remove(i);
					decDelta();
					if (entries.isEmpty()) {
						setState(State.EMPTY_LEAF);
						//tagaImpl.getIndexes().remove(tag);
						// TODO inform parent that this child is deleted
					}
					return;
				}

				/* sort entries after decrementing rank */
				int indexToSwitch = i;
				while (indexToSwitch < entries.size() - 1 && 
						entry.getRank() < entries.get(indexToSwitch + 1).getRank()) {
					indexToSwitch++;
				}

				if (indexToSwitch != i) {
					IndexEntry temp = entries.get(indexToSwitch);
					entries.setElementAt(entry, indexToSwitch);
					entries.set(i, temp);
				}
				break;
				/* end of sorting */
			}
		}
	}

	@Override
	public boolean bufferOrForward(IndexMsg msg) {
		switch(this.state) {
		case PARENT:
			msg.forward(children, tagaImpl);
			return true;
		case RETRACTING:
			msgBuffer.add(msg);
			return false;
		case LEAF:
		default:
			return false;
		}
	}
	
	/*
	 * (non-Javadoc)
	 * @see dk.itu.samd.group2.tagapastry.Index#getSize()
	 */
	public int getIndexSize() {
		if(this.state == Index.State.PARENT) {
			sizeOfObject = 0; // Not really, include children!
			for(int i=0; i < children.size(); i++) {
				sizeOfObject += children.get(i).getSize();
			}
			return sizeOfObject;
		}
		
		sizeOfObject = 0;
		for(IndexEntry ie : entries) {
			sizeOfObject += ie.getSize();
		}
		
		return sizeOfObject;
	}

	@Override
	public void handleRetractionResponse(Vector<IndexEntry> entries, char childExtension) {
		this.entries = Merger.merge(this.entries, entries);
		ChildrenMapper mapper = new ChildrenMapper();	
		int index = mapper.get(childExtension);
		children.get(index).deactivate();

		// Checks if finished retracting
		int inactiveChildren = 0;
		for (IndexRelative child : children) {
			if (child.isInactive()) {
				inactiveChildren++;
			}
		}
		if (inactiveChildren == mapper.getChildrenNum()) {
			setState(State.LEAF);
			//System.out.println("executing buffered retraction messages");
			for (TagapastryMsg msg : msgBuffer) {
				System.out.println("    ---  " + msg);
				msg.execute(tagaImpl);
			}
			
			// Don't forget to empty buffer :-O
			msgBuffer.clear();
		}

	}

	@Override
	public void cleanDataInChild(char childExtension) {
		int relevantCharNum = tag.getExtension().length();
		for (int i = 0; i < entries.size(); i++) {
			IndexEntry entry = entries.get(i);
			if (entry.getHash().charAt(relevantCharNum) == childExtension) {
				entries.remove(i);
				i--;
			} 
		}
	}

	@Override
	public Iterator<IndexEntry> iterator() {
		return entries.iterator();
	}

	@Override
	public boolean isEmpty() {
		return this.getSize() == 0;
	}

}